package com.ljh.lottery.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ljh.lottery.common.errorcode.ServiceErrorCodeConstants;
import com.ljh.lottery.common.exception.ServiceException;
import com.ljh.lottery.common.utils.BeanTransUtil;
import com.ljh.lottery.common.utils.JacksonUtil;
import com.ljh.lottery.common.utils.RedisUtil;
import com.ljh.lottery.mapper.*;
import com.ljh.lottery.pojo.dataobject.*;
import com.ljh.lottery.pojo.request.CreateActivityRequest;
import com.ljh.lottery.pojo.request.CreatePrizeByActivityRequest;
import com.ljh.lottery.pojo.request.CreateUserByActivityRequest;
import com.ljh.lottery.pojo.request.PageRequest;
import com.ljh.lottery.service.ActivityService;
import com.ljh.lottery.service.dto.ActivityDTO;
import com.ljh.lottery.service.dto.ActivityDetailDTO;
import com.ljh.lottery.service.dto.CreateActivityDTO;
import com.ljh.lottery.service.dto.PageListDTO;
import com.ljh.lottery.service.enums.ActivityPrizeTiersEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * @author: Themberfue
 * @date: 2025/4/13 20:17
 * @description:
 */
@Service
public class ActivityServiceImpl implements ActivityService {

    private static final Logger log = LoggerFactory.getLogger(ActivityServiceImpl.class);
    /**
     * 活动缓存前置，为了区分业务
     */
    private final String ACTIVITY_PREFIX = "ACTIVITY_";

    /**
     * 活动缓存过期时间：3day
     */
    private final Long ACTIVITY_TIMEOUT = 60 * 60 * 24 * 3L;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PrizeMapper prizeMapper;

    @Autowired
    private ActivityMapper activityMapper;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private ActivityUserMapper activityUserMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    @Transactional(rollbackFor = Exception.class) // ! 设计多表操作，开启事务
    public CreateActivityDTO createActivity(CreateActivityRequest param) {
        // TODO 1. 校验活动信息是否正确
        checkActivityInfo(param);

        // TODO 2. 保存活动信息
        ActivityDO activityDO = BeanTransUtil.transToActivityDO(param);
        activityMapper.insert(activityDO);

        // TODO 3. 保存活动关联的奖品信息
        List<ActivityPrizeDO> prizes = param.getActivityPrizeList()
                .stream()
                .map(item -> BeanTransUtil.transToActivityPrizeDO(item, activityDO.getId()))
                .toList();
        activityPrizeMapper.insert(prizes);

        // TODO 4. 保存活动关联的人员信息
        List<ActivityUserDO> users = param.getActivityUserList()
                .stream()
                .map(item -> BeanTransUtil.transToActivityUserDO(item, activityDO.getId()))
                .toList();
        activityUserMapper.insert(users);

        // TODO 5. 整合完整的信息，存入 Redis 缓存
        // ! activityId: ActivityDetailDTO: 活动 + 奖品+ 人员
        // TODO 5.1 获取奖品基本属性列表
        List<Long> prizeIds = param.getActivityPrizeList()
                .stream()
                .map(CreatePrizeByActivityRequest::getPrizeId)
                .distinct() // ? 得到的 ID 去重
                .toList();
        List<PrizeDO> prizeDOS = prizeMapper.selectByIds(prizeIds);
        // TODO 5.2 得到活动详细信息
        ActivityDetailDTO activityDetailDTO = BeanTransUtil.transToActivityDetailDTO(activityDO, prizeDOS,
                users, prizes);
        // TODO 5.3 缓存至 Redis
        cacheActivity(activityDetailDTO);

        // TODO 6. 构造响应数据
        return new CreateActivityDTO(activityDO.getId());
    }

    @Override
    public PageListDTO<ActivityDTO> getActivityPageList(PageRequest param) {
        // TODO 1. 获取奖品总数
        QueryWrapper<ActivityDO> queryWrapper = new QueryWrapper<>();
        Integer total = activityMapper.selectCount(queryWrapper)
                .intValue();

        // TODO 2. 根据偏移量和页数获取奖品列表
        Page<ActivityDO> page = new Page<>(param.getCurrentPage(), param.getPageSize());
        queryWrapper.lambda()
                .orderByDesc(ActivityDO::getId);

        Page<ActivityDO> activityDOPage = activityMapper.selectPage(page, queryWrapper);
        List<ActivityDTO> activityDTOList = activityDOPage.getRecords()
                .stream()
                .map(BeanTransUtil::transToActivityDTO)
                .toList();

        // TODO 3. 构造响应数据
        return new PageListDTO<>(total, activityDTOList);
    }

    @Override
    public ActivityDetailDTO getActivityDetail(Long activityId) {
        // TODO 1. 参数校验
        if (null == activityId) {
            log.warn("查询活动详细信息失败，activityId为空！");
            return null;
        }

        // TODO 2. 查询 Redis
        ActivityDetailDTO activityDetailDTO = getActivity(activityId);
        if (null != activityDetailDTO) {
            log.info("查询活动详细信息成功！detailDTO = {}",
                    JacksonUtil.writeValueAsString(activityDetailDTO));
            return activityDetailDTO;
        }

        // TODO 3. 如果 Redis未缓存，查 MySQL
        activityDetailDTO = queryActivityDetail(activityId);

        // TODO 4. 缓存至 Redis
        cacheActivity(activityDetailDTO);

        // TODO 5. 返回活动详细信息
        return activityDetailDTO;
    }

    /**
     * 缓存完整的活动信息 ActivityDetailDTO
     * @param activityId 活动 id
     */
    @Override
    public void cacheActivity(Long activityId) {
        // TODO 1. 校验活动 id 是否正确
        if (null == activityId) {
            log.warn("要缓存的活动id为空！");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_IS_EMPTY);
        }

        // TODO 2. 查 MySQL
        ActivityDetailDTO activityDetailDTO = queryActivityDetail(activityId);

        // TODO 3. 缓存至 Redis
        cacheActivity(activityDetailDTO);
    }

    /**
     * 缓存完整的活动信息 ActivityDetailDTO
     * @param activityDetailDTO 完整的活动信息
     */
    private void cacheActivity(ActivityDetailDTO activityDetailDTO) {
        // TODO 1. 校验完整活动信息
        if (null == activityDetailDTO || null == activityDetailDTO.getActivityId()) {
            log.warn("要缓存的活动信息不存在!");
            return;
        }

        // TODO 2. 缓存至 Redis
        try {
            String key = ACTIVITY_PREFIX + activityDetailDTO.getActivityId();
            redisUtil.set(key, JacksonUtil.writeValueAsString(activityDetailDTO), ACTIVITY_TIMEOUT);
        } catch (Exception e) { // ! 这里不希望导致抛出异常后回滚事务
            log.error("缓存活动异常，ActivityDetailDTO: {}",
                    JacksonUtil.writeValueAsString(activityDetailDTO), e);
        }
    }

    /**
     * 从数据库中查询完整的活动信息
     * @param activityId 活动 id
     * @return 完整的活动信息
     */
    private ActivityDetailDTO queryActivityDetail(Long activityId) {
        // TODO 1. 查询活动表
        ActivityDO activityDO = activityMapper.selectById(activityId);
        if (null == activityDO) {
            throw new ServiceException(ServiceErrorCodeConstants.GET_ACTIVITY_DETAIL_ERROR);
        }

        // TODO 2. 查询活动奖品表
        QueryWrapper<ActivityPrizeDO> queryActivityPrizeDO = new QueryWrapper<>();
        queryActivityPrizeDO.lambda()
                .eq(ActivityPrizeDO::getActivityId, activityId);
        List<ActivityPrizeDO> activityPrizeDOS = activityPrizeMapper.selectList(queryActivityPrizeDO);

        // TODO 3. 查询活动人员表
        QueryWrapper<ActivityUserDO> queryActivityUserDO = new QueryWrapper<>();
        queryActivityUserDO.lambda()
                .eq(ActivityUserDO::getActivityId, activityId);
        List<ActivityUserDO> activityUserDOS = activityUserMapper.selectList(queryActivityUserDO);

        // TODO 4. 查询奖品表
        List<Long> prizeIds = activityPrizeDOS.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .toList();
        List<PrizeDO> prizeDOS = prizeMapper.selectByIds(prizeIds);

        // TODO 5. 整合活动详细信息
        return BeanTransUtil.transToActivityDetailDTO(activityDO, prizeDOS, activityUserDOS, activityPrizeDOS);
    }

    /**
     * 根据活动id从缓存中获取活动详细信息
     * @param activityId 活动 id
     * @return 完整的活动信息
     */
    private ActivityDetailDTO getActivity(Long activityId) {
        // TODO 1. 校验活动 id
        if (null == activityId) {
            log.warn("获取缓存活动数据的activityId为空！");
            return null;
        }

        // TODO 2. 从 Redis 中获取完整活动信息
        try {
            String key = ACTIVITY_PREFIX + activityId;
            if (!StringUtils.hasText(key)) {
                log.info("获取的缓存活动数据为空！key: {}", ACTIVITY_PREFIX + activityId);
                return null;
            }
            return JacksonUtil.readValue(redisUtil.get(key), ActivityDetailDTO.class);
        } catch (Exception e) {
            log.error("从缓存中获取活动信息异常，key = {}", ACTIVITY_PREFIX + activityId, e);
            return null;
        }
    }

    /**
     * 校验活动有效性
     * @param param 活动请求参数
     */
    private void checkActivityInfo(CreateActivityRequest param) {
        // TODO 1. 校验活动请求
        if (null == param) {
            throw new ServiceException(ServiceErrorCodeConstants.CREATE_ACTIVITY_INFO_IS_EMPTY);
        }

        // TODO 2. 人员 id 在人员列表中是否存在
        List<Long> userIds = param.getActivityUserList()
                .stream()
                .map(CreateUserByActivityRequest::getUserId)
                .distinct() // ? 得到的 ID 去重
                .toList();
        List<Long> existsUserIds = userMapper.selectByIds(userIds)
                .stream()
                .map(UserDO::getId)
                .toList();
        // TODO 2.1 判断从数据库里获取的数据其是否为空
        if (CollectionUtils.isEmpty(existsUserIds)) {
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
        }
        existsUserIds.forEach(id -> {
            if (!userIds.contains(id))
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
        });

        // TODO 3. 奖品 id 在奖品列表是否存在
        List<Long> prizeIds = param.getActivityPrizeList()
                .stream()
                .map(CreatePrizeByActivityRequest::getPrizeId)
                .distinct() // ? 得到的 ID 去重
                .toList();
        List<Long> existsPrizeIds = prizeMapper.selectByIds(prizeIds)
                .stream()
                .map(PrizeDO::getId)
                .toList();
        // TODO 3.1 判断从数据库里获取的数据其是否为空
        if (CollectionUtils.isEmpty(existsPrizeIds)) {
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
        }
        existsPrizeIds.forEach(id -> {
            if (!prizeIds.contains(id))
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
        });

        // TODO 4. 奖品数量是否大于人员数量
        long userAmount = param.getActivityUserList().size();
        long prizeAmount = param.getActivityPrizeList()
                .stream()
                .mapToLong(CreatePrizeByActivityRequest::getPrizeAmount)
                .sum();
        if (userAmount < prizeAmount)
            throw new ServiceException(ServiceErrorCodeConstants.USER_PRIZE_AMOUNT_ERROR);

        // TODO 5. 校验活动奖品等级有效性
        param.getActivityPrizeList().forEach(item -> {
            if (null == ActivityPrizeTiersEnum.forName(item.getPrizeTiers()))
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_TIERS_ERROR);
        });
    }
}
